package com.fowo.api.common.mybatis.interceptor;

import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
import com.fowo.api.common.model.PageSearch;
import com.fowo.api.user.model.JwtUserInfo;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.Parenthesis;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.security.core.parameters.P;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * 提供数据权限过滤
 */
@Slf4j
public class RolePermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
    static final int RANGE_ALL = 1;
    static final int RANGE_CUSTOM = 2;
    static final int RANGE_DEP = 3;
    static final int RANGE_DEP_DEEP = 4;
    static final int RANGE_SELF = 5;

    private static ThreadLocal<Map<String, RolePermissionMetadata>> localMappedIdMetadataMaps = new ThreadLocal<>();

    /** 预查当前用户的所有角色的数据权限范围，用于快速查询优化 */
    private void preQueryUserRanges(Executor executor, JwtUserInfo jwtUserInfo, RolePermissionMetadata metadata) {
        try {
            // 预查
            final Connection connection = executor.getTransaction().getConnection();
            final PreparedStatement preparedStatement = connection.prepareStatement("SELECT distinct SRP.`range` FROM sys_role_permission SRP\n" +
                    "JOIN sys_user_role SUR ON SUR.del_flag = 0 and SUR.role_id = SRP.role_id\n" +
                    "WHERE SRP.del_flag = 0 and SUR.user_id = ?\n" +
                    "ORDER BY SRP.`range`");
            preparedStatement.setLong(1, jwtUserInfo.getUserId());
            final ResultSet resultSet = preparedStatement.executeQuery();
            List<Integer> ranges = new ArrayList<>();
            while (resultSet.next()) {
                ranges.add(resultSet.getInt(1));
            }
            resultSet.close();
            preparedStatement.close();

            metadata.setRanges(ranges);
        }
        catch (SQLException e) {
            log.error("处理数据范围权限预查时发生错误", e);
        }
    }

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {

        JwtUserInfo jwtUserInfo = JwtUserInfo.fromHeader();
        if (jwtUserInfo == null || jwtUserInfo.getUserId() == null || jwtUserInfo.getUserId() <= 0) {
            // 系统内部任务调用，或未登录或无效的用户编号
            return;
        }
        boolean disableDataPermissions = getDisableDataPermissions(parameter);
        if (disableDataPermissions) {
            // 当前是否忽略了数据权限
            return;
        }

        RolePermissionMetadata metadata = getMetadata(ms);

        if (metadata == null || metadata.entityClass == null || metadata.ownerUserIdColumnName == null) {
            // 没有找到实体类或实体类没有创建人字段的不处理
            return;
        }


        preQueryUserRanges(executor, jwtUserInfo, metadata);

        if (metadata.getMinRange() == null) {
            // 没有角色数据权限，默认为只看本人数据
            metadata.ranges = Collections.singletonList(RANGE_SELF);
        }
        if (metadata.getMinRange() != null && metadata.getMinRange() == RANGE_ALL) {
            // 用户的最大角色权限是全部数据，不用添加权限查询
            return;
        }

        metadata.setCurrentUserId(jwtUserInfo.getUserId());

        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
        mpBs.sql(parserSingle(mpBs.sql(), metadata));
    }

    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        RolePermissionMetadata metadata = (RolePermissionMetadata) obj;
        final SelectBody selectBody = select.getSelectBody();
        if (selectBody instanceof PlainSelect) {
            PlainSelect plainSelect = (PlainSelect) selectBody;

            List<Expression> customExpressionList = new ArrayList<>();

            Column createUserColumn = new Column("t.create_user");
            boolean hasOwnerDepartmentColumn = metadata.hasOwnerDepartmentColumn();
            LongValue currentUser = new LongValue(metadata.getCurrentUserId());

            Expression createUserEqual = new EqualsTo()
                    .withLeftExpression(createUserColumn)
                    .withRightExpression(currentUser);

            if (metadata.getMinRange() == RANGE_SELF) {
                // 只看自己
                Expression oldWhere = plainSelect.getWhere();
                plainSelect.setWhere(new AndExpression(oldWhere, createUserEqual));
                return;
            }
            if (metadata.hasRange(RANGE_CUSTOM)) {
                // 自定义可看部门
                if (hasOwnerDepartmentColumn) {
                    // 直接部门字段过滤
                    String sqlCustomIn = String.format("%s IN (\n" +
                            "                \tSELECT distinct SRPD.department_id FROM sys_role_permission_departments as SRPD\n" +
                            "                \tinner join sys_role_permission as SRP on SRP.id = role_permission_id AND SRP.del_flag = 0 AND SRP.`range` = 2\n" +
                            "                \tinner join sys_user_role as SUR on SUR.role_id = SRP.role_id\n" +
                            "                \twhere SUR.del_flag = 0 AND SUR.user_id = %d\n" +
                            "                )", metadata.ownerDepartmentColumnName, metadata.currentUserId);

                    try {
                        Expression expression = CCJSqlParserUtil.parseExpression(sqlCustomIn);
                        customExpressionList.add(expression);
                    } catch (JSQLParserException e) {
                        e.printStackTrace();
                    }
                } else {
                    String sqlCustomIn = String.format("t.create_user IN (\n" +
                            "                \tSELECT SUD.user_id FROM sys_user_department SUD\n" +
                            "                \tINNER JOIN sys_role_permission_departments as SRPD ON SRPD.department_id = SUD.department_id\n" +
                            "                \tinner join sys_role_permission as SRP on SRP.id = role_permission_id AND SRP.del_flag = 0 AND SRP.`range` = 2\n" +
                            "                \tinner join sys_user_role as SUR on SUR.role_id = SRP.role_id\n" +
                            "                \twhere SUR.del_flag = 0 AND SUR.user_id = %d\n" +
                            "                )", metadata.currentUserId);

                    try {
                        Expression expression = CCJSqlParserUtil.parseExpression(sqlCustomIn);
                        customExpressionList.add(expression);
                    } catch (JSQLParserException e) {
                        e.printStackTrace();
                    }
                }
            }
            if (metadata.hasRange(RANGE_DEP)) {
                // 看所在部门
                if (hasOwnerDepartmentColumn) {
                    // 直接部门字段过滤
                    String sqlDep = String.format("%s IN (\n" +
                            "select CSUD.department_id from sys_user_department CSUD\n" +
                            "where CSUD.user_id = %d\n)", metadata.ownerDepartmentColumnName, metadata.getCurrentUserId());

                    try {
                        Expression expression = CCJSqlParserUtil.parseExpression(sqlDep);
                        customExpressionList.add(expression);
                    } catch (JSQLParserException e) {
                        e.printStackTrace();
                    }
                } else {
                    String sqlDep = String.format("t.create_user IN (SELECT DISTINCT DSUD.user_id\n" +
                            "FROM sys_user_department DSUD\n" +
                            "JOIN sys_user_department CSUD ON CSUD.department_id = DSUD.department_id AND CSUD.user_id = %d)", metadata.getCurrentUserId());

                    try {
                        Expression expression = CCJSqlParserUtil.parseExpression(sqlDep);
                        customExpressionList.add(expression);
                    } catch (JSQLParserException e) {
                        e.printStackTrace();
                    }
                }
            }
            if (metadata.hasRange(RANGE_DEP_DEEP)) {
                // 看所在部门与下属部门
                if (hasOwnerDepartmentColumn) {
                    // 直接部门字段过滤
                    String sqlDepDeep = String.format("%s IN (\n" +
                            "SELECT DISTINCT id\n" +
                            "FROM sys_department SD\n" +
                            "JOIN sys_user_department SUD ON SUD.user_id = %d AND (\n" +
                            "\tSD.id = SUD.department_id OR JSON_CONTAINS(SD.parents, CAST(SUD.department_id AS CHAR))\t\n" +
                            ")\n" +
                            // 不应该因为删除部门造成数据不可用
                            // "WHERE SD.del_flag = 0\n" +
                            ")", metadata.ownerDepartmentColumnName, metadata.getCurrentUserId());

                    try {
                        Expression expression = CCJSqlParserUtil.parseExpression(sqlDepDeep);
                        customExpressionList.add(expression);
                    } catch (JSQLParserException e) {
                        e.printStackTrace();
                    }
                } else {
                    String sqlDepDeep = String.format("t.create_user IN (SELECT DISTINCT user_id\n" +
                            "FROM sys_user_department\n" +
                            "WHERE department_id IN (\n" +
                            "SELECT DISTINCT id\n" +
                            "FROM sys_department SD\n" +
                            "JOIN sys_user_department SUD ON SUD.user_id = %d AND (\n" +
                            "\tSD.id = SUD.department_id OR JSON_CONTAINS(SD.parents, CAST(SUD.department_id AS CHAR))\t\n" +
                            ")\n" +
                            // 不应该因为删除部门造成数据不可用
                            // "WHERE del_flag = 0\n" +
                            "))", metadata.getCurrentUserId());

                    try {
                        Expression expression = CCJSqlParserUtil.parseExpression(sqlDepDeep);
                        customExpressionList.add(expression);
                    } catch (JSQLParserException e) {
                        e.printStackTrace();
                    }
                }
            }
            if (metadata.hasRange(RANGE_SELF)) {
                // 看自己
                customExpressionList.add(createUserEqual);
            }

            if (customExpressionList.isEmpty()) {
                // 没有需要添加的条件，跳过后继 SQL 处理
                return;
            }

            // 为生成的多组 SQL 条件进行 OR 打组
            Parenthesis whereGroup = new Parenthesis();
            OrExpression orExpression = null;
            for(Expression customExpression : customExpressionList) {
                if (whereGroup.getExpression() == null) {
                    whereGroup.setExpression(customExpression);
                }
                else if (orExpression == null) {
                    orExpression = new OrExpression()
                            .withLeftExpression(whereGroup.getExpression())
                            .withRightExpression(customExpression);
                    whereGroup.setExpression(orExpression);
                }
                else {
                    Expression rightExpression = orExpression.getRightExpression();
                    orExpression.setRightExpression(new OrExpression()
                        .withLeftExpression(rightExpression)
                        .withRightExpression(customExpression)
                    );
                    orExpression = orExpression.getRightExpression(OrExpression.class);
                }
            }

            // 生成的条件添加到 WHERE
            Expression oldWhere = plainSelect.getWhere();
            if (oldWhere == null) {
                plainSelect.setWhere(whereGroup.getExpression());
            } else {
                plainSelect.setWhere(new AndExpression()
                    .withLeftExpression(oldWhere)
                    .withRightExpression(whereGroup)
                );
            }
        }
    }

    private RolePermissionMetadata getMetadata(MappedStatement ms) {
        final String id = ms.getId();
        Map<String, RolePermissionMetadata> mappedIdMetadataMaps = localMappedIdMetadataMaps.get();
        if (mappedIdMetadataMaps != null && mappedIdMetadataMaps.containsKey(id)) {
            return mappedIdMetadataMaps.get(id);
        }

        RolePermissionMetadata metadata = new RolePermissionMetadata();
        if (id.endsWith("Count") && ms.getConfiguration() != null) {
            // 当前查询是合计接口，查看是否有对应的 Page 结束 MappedStatement
            String baseMsId = id.substring(0, id.length() - 5);
            String pageId = baseMsId + "Page";
            MappedStatement otherMs = ms.getConfiguration().getMappedStatement(pageId);
            if (otherMs != null && otherMs.getResultMaps() != null && otherMs.getResultMaps().size() > 0) {
                metadata.setEntityClass(otherMs.getResultMaps().get(0).getType());
            }
            if (otherMs == null) {
                otherMs = ms.getConfiguration().getMappedStatement(baseMsId);
                if (otherMs != null && otherMs.getResultMaps() != null && otherMs.getResultMaps().size() > 0) {
                    metadata.setEntityClass(otherMs.getResultMaps().get(0).getType());
                }
            }
        }
        if (metadata.getEntityClass() == null && ms.getResultMaps() != null && ms.getResultMaps().size() > 0) {
            metadata.setEntityClass(ms.getResultMaps().get(0).getType());
        }

        if (metadata.getEntityClass() != null) {
            final Field createUser = ReflectionUtils.findField(metadata.getEntityClass(), "createUser");
            if (createUser != null && createUser.getType().equals(Long.class)) {
                metadata.setOwnerUserIdColumnName("t.create_user");
            }
            RolePermissionOwnerDeptIdColumn annotationDeptIdColumn =  metadata.getEntityClass().getAnnotation(RolePermissionOwnerDeptIdColumn.class);
            if (annotationDeptIdColumn != null) {
                metadata.setOwnerDepartmentColumnName(annotationDeptIdColumn.value());
            } else {
                final Field ownerDeptId = ReflectionUtils.findField(metadata.getEntityClass(), "ownerDeptId");
                if (ownerDeptId != null) {
                    metadata.setOwnerDepartmentColumnName("t.owner_dept_id");
                }
            }
        }

        if (mappedIdMetadataMaps == null) {
            mappedIdMetadataMaps = new HashMap<>();
            localMappedIdMetadataMaps.set(mappedIdMetadataMaps);
        }
        mappedIdMetadataMaps.put(id, metadata);

        return metadata;
    }

    private boolean getDisableDataPermissions(Object parameter) {
        if (parameter != null && Map.class.isAssignableFrom(parameter.getClass())) {
            final Optional optional = ((Map) parameter).values().stream().filter(p -> p != null && PageSearch.class.isAssignableFrom(p.getClass())).findFirst();
            if (optional.isPresent()) {
                return ((PageSearch) optional.get()).isDisableDataPremissions();
            }
        }
        return true;
    }

    /**
     * 保存特定 mappedId 对应的元数据信息
     */
    @Data
    @Accessors(chain = true)
    static class RolePermissionMetadata {
        /**
         * 当前用户编号
         */
        private Long currentUserId;
        /**
         * 当前用户数据权限范围（从小到大）
         */
        private List<Integer> ranges;
        /**
         * 实体类
         */
        private Class<?> entityClass;
        /**
         * 数据所有者列名
         */
        private String ownerUserIdColumnName;
        /**
         * 数据所有部门列名
         */
        private String ownerDepartmentColumnName;

        public Integer getMinRange() {
            if (!isEmptyRange()) {
                return ranges.get(0);
            }
            return null;
        }

        public boolean isEmptyRange() {
            return ranges == null || ranges.isEmpty();
        }

        public boolean hasRange(int range) {
            if (!isEmptyRange()) {
                return ranges.contains(range);
            }
            return false;
        }

        public boolean hasOwnerDepartmentColumn() {
            return StringUtils.hasText(ownerDepartmentColumnName);
        }
    }
}
